import 'reflect-metadata'
import { inject, Container, interfaces, ContainerModule } from 'inversify';
import { fluentProvide, buildProviderModule } from "inversify-binding-decorators";
import bindingInterfaces from 'inversify-binding-decorators/dts/interfaces/interfaces';

const sTime = new Date().getTime();

const Component = function (id: string) {
  return fluentProvide(id)
    .inSingletonScope()
    .done();
}

const Autowired = function (id: string): any {
  return function (target: Object, methodName: string, descriptor: PropertyDescriptor) {
    inject(id)(target, methodName);
  }
}

@Component("a")
export class A {

  ma() {
    console.log("# ma");
  }
}

@Component("b")
export class B {

  @Autowired("a")
  a!: A;

  mb() {
    console.log("# mb");
    if (this.a) {
      this.a.ma();
    }
  }
}

function resolve(metadata: bindingInterfaces.ProvideSyntax, bind: interfaces.Bind, rebind: interfaces.Rebind): void {
  const isRebind: boolean = Reflect.getOwnMetadata("malagu:rebind", metadata.implementationType);
  const id = Reflect.getOwnMetadata("malagu:to-service", metadata.implementationType);

  const bindWrapper = (serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
    if (id && id !== serviceIdentifier) {
      bind(serviceIdentifier).toService(id);
      throw new Error();
    }
    if (isRebind) {
      return rebind(serviceIdentifier);
    }

    return bind(serviceIdentifier);
  };
  try {
    metadata.constraint(bindWrapper, metadata.implementationType);
  } catch (error) {
    if (error instanceof Error) {
      return;
    }
    throw error;
  }
}

const container = new Container();
if (false) {
  container.load(buildProviderModule());
} else {
  const module = new ContainerModule((bind, unbind, isBound, rebind) => {
    const metas: bindingInterfaces.ProvideSyntax[] = Reflect.getMetadata("inversify-binding-decorators:provide", Reflect);
    metas.map(metadata => resolve(metadata, bind, rebind));
  });
  container.load(module);
}

const b: B = container.get<B>("b");
b.mb();

const eTime = new Date().getTime();
console.log("# --------------------------------------- ", (eTime - sTime), "ms");



